#----------------------------------------------------------------------------
# wxPita - wxPython wrapper library for painless layout and event binding
#
# Author:       Feihong Hsu
# Email:        hsu DOT feihong AT yahoo DOT com
# Website:      http://wxpita.googlecode.com/
# Created:      2007 Dec 15
# Copyright:    (c) 2007 by Feihong Hsu
# License:      BSD License
#----------------------------------------------------------------------------
import wx

sizer_arg_names = ('proportion', 'flag', 'border')

def get_sizer_args(args):
    res = {}
    for k, v in args.items():
        if k in sizer_arg_names:
            if isinstance(v, basestring):
                if k == 'proportion':
                    res[k] = int(v)
                elif k == 'flag':
                    res[k] = parse_flag_string(v)
                elif k == 'border':
                    res[k] = getattr(wx, v.upper())
            else:
                res[k] = v
    return res

def convert_ctor_args(args):
    "Convert strings to their wx.* equivalents"
    for k, v in args.items():
        if isinstance(v, basestring):
            if k == 'size':
                args[k] = eval(v)
            elif k == 'pos':
                args[k] = eval(v)
            elif k == 'style':
                args[k] = parse_flag_string(v)

def get_constant(name):
    for module in (wx, wx.calendar, wx.grid):
        value = getattr(module, name, None)
        if value:
            return value
    return None
                
def parse_flag_string(text):
    names = (v.strip().upper() for v in text.split('|'))
    flag = 0
    for name in names:
        flag |= get_constant(name)
    return flag

def multilistener(funcs):
    def func(evt):
        for f in funcs:
            f(evt)
    return func
    
def get_event_type_id(name):
    evt_type_name = 'wxEVT_COMMAND_' + name.upper()
    code = get_constant(evt_type_name)
    if not code:
        evt_binder_name = 'EVT_' + name.upper()
        evt_binder = get_constant(evt_binder_name)
        if evt_binder:
            code = evt_binder.evtType[0]
    return code

class EventRecorder(object):
    def __init__(self, wrapper, name):
        self.wrapper = wrapper
        self.evt_type_id = get_event_type_id(name)

    def register(self, func):
        self.wrapper.events.append((self.evt_type_id, func))
        
    def __call__(self, func):
        self.wrapper.events.append((self.evt_type_id, func))
        return func

ctor_arg_names = ('id', 'name', 'pos', 'size', 'style', 'validator')

class WxWrapper(object):
    def __init__(self, winforms_class, ctor_arg_names=None):
        self.cls = winforms_class
        self.args = {}
        self.widget = None
        self.events = []
        if ctor_arg_names:
            self.ctor_arg_names = [s.strip() for s in ctor_arg_names.split(',')]
        else:
            self.ctor_arg_names = ()

    def get_ctor_args(self):
        result = {}
        for k, v in self.args.items():
            if k in ctor_arg_names or k in self.ctor_arg_names:
                result[k] = v
        if 'id' not in result:  # we do this for widgets like wx.lib.Calendar
            result['id'] = -1
        return result
        
    def is_container(self): return False

    def __getattr__(self, name):
        # If the widget has already been created, this wrapper object just
        # acts as its proxy:
        if self.widget:
           return getattr(self.widget, name)
        else:
            return EventRecorder(self, name)

    def copy(self):
        # copy.deepcopy has problems with the __getattr__ method
        cp = self.__class__(self.cls)
        cp.args = dict(item for item in self.args.items())
        cp.ctor_arg_names = self.ctor_arg_names
        return cp

    def __call__(self, **kwargs):
        wrapper = self.copy()
        wrapper.args.update(kwargs)
        return wrapper

    def CreateWidget(self, parent):
        self.modify_args()
        self.widget = self.cls(parent, **self.get_ctor_args())
        self.set_widget_attributes()
        self.bind_events()
        return self.widget

    def modify_args(self):
        """Override this method to modify the self.args right before
        widget creation"""
        convert_ctor_args(self.args)
        
    def set_widget_attributes(self):
        "Override this method to modify the widget right after its creation"

    def bind_events(self):
        events = {}
        # Group all listeners for the same event together:
        for code, func in self.events:
            if code in events:
                events[code].append(func)                
            else:
                events[code] = [func]
                
        for code, funcs in events.items():
            if len(funcs) == 1:
                self.widget.Connect(wx.ID_ANY, wx.ID_ANY, code, funcs[0])
            else:
                self.widget.Connect(wx.ID_ANY, wx.ID_ANY, code, multilistener(funcs))
            
    def Show(self, title="", size=None):
        app = wx.PySimpleApp()

        if issubclass(self.cls, wx.Frame):
            if title:
                self.args['title'] = title
            frame = self.CreateWidget(None)
        elif issubclass(self.cls, wx.Panel):
            frame = wx.Frame(None, title=title)
            panel = self.CreateWidget(frame)
        else:
            frame = wx.Frame(None, title=title)
            panel = wx.Panel(frame)
            widget = self.CreateWidget(panel)

        if size == "fit":
            frame.Fit()
        elif size:
            frame.SetSize(size)
        
        frame.Show(True)
        app.MainLoop()

    def __str__(self):
        return "<%s '%s'>" % (self.__class__.__name__, self.cls.__name__)

class ContainerWrapper(WxWrapper):
    def __init__(self, winforms_class, ctor_arg_names=None):
        super(ContainerWrapper, self).__init__(winforms_class, ctor_arg_names)
        self.children = []
        self.member_widgets = {}
        self.init_callbacks = []
        
    def is_container(self): return True

    def __getitem__(self, children):
        wrapper = self.copy()
        children = children if isinstance(children, (tuple,list)) else (children,)

        for child in children:                
            wrapper.children.append(child)
            wrapper.add_member(child)

        return wrapper

    def CreateWidget(self, parent):
        self.modify_args()
        self.widget = self.cls(parent, **self.get_ctor_args())
        self.set_widget_attributes()
        self.add_children()
        
        for name, wrapper in self.member_widgets.items():
            setattr(self, name, wrapper.widget)
            setattr(self.widget, name, wrapper.widget)
            
        for callback in self.init_callbacks:
            callback()
        
        self.bind_events()
        return self.widget

    def add_children(self):
        "Subclasses should override this behavior when necessary."
        for child in self.children:
            child.CreateWidget(self.widget)

    def add_member(self, wrapper):
        if 'name' in wrapper.args:
            name = wrapper.args['name']
            self.member_widgets[name] = wrapper
            setattr(self, name, wrapper)
            
        if wrapper.is_container():
            for name, child in wrapper.member_widgets.items():
                self.member_widgets[name] = child
                setattr(self, name, child)

    def Init(self, func):
        self.init_callbacks.append(func)

##------------------------------------------------------------------------------
if __name__ == '__main__':
    pass
